package org.apache.shiro.web.filter.user;

import com.liwbn.rbac.common.conf.Constants;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.cache.Cache;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.*;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionException;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
import java.util.Deque;
import java.util.LinkedList;

/**
 * 用于限制用户重复登陆,根据配置将重复登陆用户踢出
 * Created by liwb on 2016-8-4.
 */
public class KickoutUserFilter extends AccessControlFilter {

    private static final Logger LOGGER = LoggerFactory
            .getLogger(KickoutUserFilter.class);

    /**
     * 获取缓存对象来管理登陆用户的sessionId，根据sessionId来判断登陆次数
     * 注意session销毁时删除对应sessionId
     */
    private EhCacheManager ehCacheManager;
    // 储存登陆用户信息
    private Cache<Object, Deque<Serializable>> cache;
    // 缓存名字
    private String cacheName;
    // 最大用户同时在线数
    private Integer maxSession;
    // 是否从后者开始清除
    private boolean kickoutAfter = false;

    private SessionManager sessionManager;

    public KickoutUserFilter(EhCacheManager ehCacheManager,
            String authorizationCacheName, Integer maxSession,
            boolean kickoutAfter) {
        this.ehCacheManager = ehCacheManager;
        this.cacheName = authorizationCacheName;
        this.maxSession = maxSession;
        this.kickoutAfter = kickoutAfter;
        this.cache = ehCacheManager.getCache(cacheName);
        LOGGER.info("获取cache缓存[{}]", cacheName);
    }

    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest,
            ServletResponse servletResponse, Object o) throws Exception {

        return true;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest,
            ServletResponse servletResponse) throws Exception {
        Subject subject = SecurityUtils.getSubject();
        if (!subject.isAuthenticated() && !subject.isRemembered()) {
            return true;
        }

        Session session = subject.getSession();
        Serializable sessionId = session.getId();
        Object currentUser = subject.getPrincipal();

        Deque<Serializable> deque = cache.get(currentUser);
        if (deque == null) {
            deque = new LinkedList<Serializable>();
            cache.put(currentUser, deque);
        }
        if (session.getAttribute(Constants.CURRENT_USER) == null) {
            session.setAttribute(Constants.CURRENT_USER, currentUser);
        }

        // 将sessionId放入缓存
        if (!deque.contains(currentUser)
                && session.getAttribute("kickout") == null) {
            deque.push(sessionId);
        }

        // 开始踢人流程
        Serializable kickoutSessionId;
        Session kickoutSession;
        while (deque.size() > maxSession) {
            if (kickoutAfter) {
                // 从最新登陆的用户开始清除
                kickoutSessionId = deque.getLast();
            } else {
                // 从登陆最久的用户开始清除
                kickoutSessionId = deque.getFirst();
            }
            try {
                kickoutSession = sessionManager
                        .getSession(new DefaultSessionKey(kickoutSessionId));
                if (kickoutSession != null) {
                    kickoutSession.setAttribute("kickout", true);
                }
            } catch (SessionException e) {
                LOGGER.error(e.getMessage());
            }
        }

        // 判断是否是标记退出的session
        if (session.getAttribute("kickout") != null) {
            try {
                subject.logout();
            } catch (Exception e) {
                LOGGER.error(e.getMessage());
            }
            return false;
        }
        return true;
    }
}
